package cn.jingyuan.swan.gw.manager;

import cn.jingyuan.bee.utils.ObjectUtils;
import cn.jingyuan.bee.utils.StringUtils;
import cn.jingyuan.swan.gw.locator.ResourceLocator;
import cn.jingyuan.swan.system.model.dto.AuthorityResourceDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

/**
 * 访问检查
 */
@Slf4j
public class AccessCheckManager {

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    /** 总是允许 */
    private final Set<String> alwaysPermitAuthorities = new HashSet<>();

    /** 忽略权限 */
    private final Set<String> ignoreAuthorities = new HashSet<>();

    private final ResourceLocator resourceLocator;

    public AccessCheckManager(ResourceLocator resourceLocator) {
        Assert.notNull(resourceLocator, "resourceLocator can't be null");
        this.resourceLocator = resourceLocator;
    }

    public AccessCheckManager(ResourceLocator resourceLocator, Collection<String> ignoreAuthorities) {
        Assert.notNull(resourceLocator, "resourceLocator can't be null");
        this.resourceLocator = resourceLocator;

        if (ObjectUtils.isNotEmpty(ignoreAuthorities)) {
            this.ignoreAuthorities.addAll(ignoreAuthorities);
        }
    }

    /**
     * 访问检查
     *
     * @param request 请求
     * @param authentication 认证信息
     *
     * @return 是否通过检查
     */
    public boolean accessChecking(HttpServletRequest request, Authentication authentication) {
        String requestPath = this.getRequestPath(request);
        // 1、根据具体需求进行过滤
        return doCheck(request, authentication, requestPath);
    }

    /**
     * 添加不进行权限检查的请求
     *
     * @param ignoreAuthorities 无需权限即可访问的集合
     */
    public void addIgnoreAuthorities(Collection<String> ignoreAuthorities) {
        this.ignoreAuthorities.addAll(ignoreAuthorities);
    }

    /**
     * 获取请求路径
     *
     * @param request 请求
     *
     * @return 请求路径
     */
    public String getRequestPath(HttpServletRequest request) {
        String servletPath = request.getServletPath();
        String pathInfo = request.getPathInfo();
        if (null != pathInfo) {
            servletPath = StringUtils.isNotBlank(servletPath) ? servletPath + pathInfo : pathInfo;
        }
        return servletPath;
    }

    /**
     * 检查权限
     *
     * @param request 请求
     * @param authentication 认证信息
     * @param path 路径
     *
     * @return 是否具有访问权限
     */
    private boolean doCheck(HttpServletRequest request, Authentication authentication, String path) {
        // FIXME 此处仅仅为标记说明，不需要进行修复：所有权限相关的检查扩展点都放在此处，譬如-->限流 | 权限检查

        /*
            1、检查流程：
                a)：限流检查
                        全局限流
                        其他限流
                b)：流量检查（个人提出）
                c)：访问权限
         */

        Object principal = authentication.getPrincipal();
        boolean noNullPrincipal = (null != principal);
        boolean isAnonymous = (noNullPrincipal && authentication instanceof AnonymousAuthenticationToken);

        // 限流检查
        {
            // TODO 待计划纳入
            log.debug("限流检查：暂未实现，忽略检查！");
        }

        // 访问检查
        {
            // 检查可以无权访问
            if (this.checkNoAuthority(path)) {
                return true;
            }

            // 检查可以匿名访问
            if (isAnonymous) {
                if (this.checkAnonymous(path)) {
                    return true;
                }
            }

            // 此处进行 “用户” 权限检查
            // boolean permit = mathAuthorities(request, authentication, path);
            return true;
        }
    }


    /**
     * 获取资源
     *
     * @param requestPath 请求路径
     *
     * @return 资源
     */
    public AuthorityResourceDto getAuthorityResource(String requestPath) {
        Stream<AuthorityResourceDto> stream =
            resourceLocator.getAllAuthorityResources().stream();

        return
            stream
                .filter(dto -> StringUtils.isNotBlank(dto.getPath()))
                .filter(dto -> !"/**".equals(dto.getPath()))
                .filter(dto -> pathMatcher.match(dto.getPath(), requestPath))
                .filter(dto -> !checkNoAuthority(dto.getPath()))
                .findFirst()
                .orElse(null);
    }


    private boolean checkNoAuthority(String path) {
        return
            ignoreAuthorities.stream()
                .filter(r -> pathMatcher.match(r, path))
                .findFirst()
                .isPresent();
    }

    /**
     * 访问检查：可匿名访问
     *
     * @param path path
     *
     * @return true：可以访问，false：不可访问
     */
    private boolean checkAnonymous(String path) {
        // check if this path can be access by anonymous
        // FIXME 伪逻辑
        return false;
    }

}
